package club.kynb.mall.payment.impl;

import club.kynb.mall.payment.exec.PayExecutorFactory;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import club.kynb.mall.payment.api.IPayService;
import club.kynb.mall.payment.constant.PayOrderTypeEnum;
import club.kynb.mall.payment.constant.TradeOrderStatusEnum;
import club.kynb.mall.payment.dto.ext.*;
import club.kynb.mall.payment.dto.third.PayExecutorQueryResultDTO;
import club.kynb.mall.payment.dto.third.PayExecutorResultDTO;
import club.kynb.mall.payment.mysql.mapper.PayTradeOrderMapper;
import club.kynb.mall.payment.mysql.po.PayTradeOrderPO;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.pizza.common.web.exception.Errors;
import org.pizza.id.api.IdGenerator;
import org.pizza.util.Checker;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.List;
import java.util.Optional;

/**
 * @author kynb_club@163.com
 * @date: 2021/7/1
 */
@Slf4j
@Service
@AllArgsConstructor
public class PayServiceImpl implements IPayService {

    private final PayTradeOrderMapper payTradeOrderMapper;
    private final RedissonClient redissonClient;
    private final PayExecutorFactory payExecutorFactory;
    private final IdGenerator idGenerator;
    private static final long RECREATE_ORDER_INTERVAL = 120;
    private static final String LOCK_KEY_PREFIX = "PAY_ORDER:";

    @Override
    public PayResultDTO createPay(PayRequestDTO dto) {
        //1. 已支付校验
        rePayCheck(dto.getOrderNo(),dto.getOrderType());
        //2. 支付中校验
        wait4UpdateCheck(dto.getOrderNo(),dto.getOrderType());
        //3. 创建流水
        RLock lock = redissonClient.getLock(LOCK_KEY_PREFIX + dto.getOrderNo());
        boolean isLocked = false;
        try {
            Checker.ifNotThrow(isLocked = lock.tryLock(),()->Errors.BIZ.exception("操作频繁，稍后再试"));
            PayTradeOrderPO tradeOrder = new PayTradeOrderPO();
            tradeOrder.setId(idGenerator.nextId(PayTradeOrderPO.class));
            tradeOrder.setOriginOrderType(dto.getOrderType().getCode());
            tradeOrder.setOriginOrderNo(dto.getOrderNo());
            tradeOrder.setUserId(dto.getUserId());
            tradeOrder.setOrderAmount(dto.getPayAmount());
            tradeOrder.setStatus(TradeOrderStatusEnum.WAITING_4_UPDATE.getStatus());
            tradeOrder.setPayChannel(dto.getPayChannel().getCode());
            tradeOrder.setClientType(dto.getPayClientType().getCode());
            tradeOrder.setTradeOrderNo(idGenerator.nextId(dto.getOrderType().getPrefix(),PayTradeOrderPO.class));
            tradeOrder.setCreateTime(new Date());
            payTradeOrderMapper.insert(tradeOrder);

            //4. 调用支付
            PayExecutorResultDTO executorResultDTO = payExecutorFactory.getService(dto.getPayChannel().getCode()).createPay(dto,tradeOrder);
            Checker.ifThen(TradeOrderStatusEnum.FAILURE.equals(executorResultDTO.getStatus()),(t)->{
                tradeOrder.setPayAmount(executorResultDTO.getPayAmount());
                tradeOrder.setBizParams(executorResultDTO.getParams());
                tradeOrder.setExtension(executorResultDTO.getReturnInfo());
                tradeOrder.setStatus(executorResultDTO.getStatus().getStatus());
                payTradeOrderMapper.updateById(tradeOrder);
                throw Errors.SYSTEM.exception("支付通道调用失败");
            });
            tradeOrder.setStatus(executorResultDTO.getStatus().getStatus());
            tradeOrder.setPayAmount(executorResultDTO.getPayAmount());
            tradeOrder.setPayTime(new Date());
            tradeOrder.setPayFlowNo(executorResultDTO.getPayFlowNo());
            tradeOrder.setBizParams(executorResultDTO.getParams());
            tradeOrder.setExtension(executorResultDTO.getReturnInfo());
            payTradeOrderMapper.updateById(tradeOrder);

            PayResultDTO resultDTO = new PayResultDTO();
            resultDTO.setOrderNo(dto.getOrderNo());
            resultDTO.setPayOrderNo(tradeOrder.getTradeOrderNo());
            resultDTO.setPayAmount(dto.getPayAmount());
            resultDTO.setStatus(executorResultDTO.getStatus());
            resultDTO.setPayData(executorResultDTO.getPayData());
            return resultDTO;
        } finally {
            if(isLocked){
                lock.unlock();
            }
        }
    }

    private void wait4UpdateCheck(String orderNo, PayOrderTypeEnum orderTypeEnum){
        List<PayTradeOrderPO> tradeOrderList = this.payTradeOrderMapper
                .selectList(new LambdaQueryWrapper<PayTradeOrderPO>().eq(PayTradeOrderPO::getOriginOrderNo, orderNo)
                        .eq(PayTradeOrderPO::getStatus, TradeOrderStatusEnum.WAITING_4_UPDATE.getStatus()).orderByAsc(PayTradeOrderPO::getId));
        if(CollectionUtil.isEmpty(tradeOrderList)){
            return;
        }
        for (PayTradeOrderPO tradeOrder : tradeOrderList) {
            long intervalSecond = DateUtil.between(new Date(), tradeOrder.getCreateTime(), DateUnit.SECOND);
            Checker.ifThrow(intervalSecond < RECREATE_ORDER_INTERVAL,()-> Errors.BIZ.exception("付款发起过于频繁，请稍后再试"));
            // 检查原订单支付结果
            PayQueryResultDTO query = this.query(tradeOrder);
            //时间大于1分钟，可忽略中间状态
            Checker.ifThrow(TradeOrderStatusEnum.SUCCESS.equals(query.getStatus()),()->Errors.BIZ.exception("该订单已付款"));
            this.payTradeOrderMapper.updateById(tradeOrder.setStatus(-1));
        }
    }

    private void rePayCheck(String orderNo, PayOrderTypeEnum orderTypeEnum){
        PayTradeOrderPO tradeOrder = this.payTradeOrderMapper
                .selectOne(new LambdaQueryWrapper<PayTradeOrderPO>()
                        .eq(PayTradeOrderPO::getOriginOrderNo, orderNo)
                        .eq(PayTradeOrderPO::getOriginOrderType,orderTypeEnum.getCode())
                        .eq(PayTradeOrderPO::getStatus, TradeOrderStatusEnum.SUCCESS.getStatus()).last("limit 1"));
        Checker.ifThrow(tradeOrder != null,()-> Errors.BIZ.exception("该订单已付款"));
    }

    @Override
    public PayQueryResultDTO query(PayQueryDTO query) {

        List<PayTradeOrderPO> tradeOrderList = this.payTradeOrderMapper
                .selectList(new LambdaQueryWrapper<PayTradeOrderPO>()
                        .eq(PayTradeOrderPO::getOriginOrderNo, query.getOrderNo())
                        .eq(PayTradeOrderPO::getOriginOrderType,query.getOrderType().getCode())
                        .orderByDesc(PayTradeOrderPO::getId)
                );
        Optional<PayTradeOrderPO> successOrderOptional = tradeOrderList.stream().filter(tradeOrder -> TradeOrderStatusEnum.SUCCESS.getStatus().equals(tradeOrder.getStatus())).findFirst();
        if(successOrderOptional.isPresent()){
            PayTradeOrderPO tradeOrder = successOrderOptional.get();
            return new PayQueryResultDTO().setTradeOrderNo(tradeOrder.getTradeOrderNo())
                    .setPayAmount(tradeOrder.getPayAmount())
                    .setStatus(TradeOrderStatusEnum.get(tradeOrder.getStatus()))
                    .setPayTime(tradeOrder.getPayTime());
        }
        PayTradeOrderPO tradeOrder = tradeOrderList.get(0);
        if(!TradeOrderStatusEnum.WAITING_4_UPDATE.getStatus().equals(tradeOrder.getStatus())){
            return new PayQueryResultDTO().setTradeOrderNo(tradeOrder.getTradeOrderNo())
                    .setPayAmount(tradeOrder.getPayAmount())
                    .setStatus(TradeOrderStatusEnum.get(tradeOrder.getStatus()))
                    .setPayTime(tradeOrder.getPayTime());
        }
        return query(tradeOrder);
    }

    private PayQueryResultDTO query(PayTradeOrderPO tradeOrder){
        log.info("payChannelQuery[1] start -- orderNo:{}",tradeOrder.getOriginOrderNo());
        PayExecutorQueryResultDTO result = payExecutorFactory.getService(tradeOrder.getPayChannel()).query( tradeOrder);
        log.info("payChannelQuery[2] result :{}", JSONUtil.toJsonStr(result));
        if (TradeOrderStatusEnum.SUCCESS.equals(result.getStatus())) {
            tradeOrder.setPayTime(result.getPayTime());
            tradeOrder.setPayAmount(result.getPayAmount());
        }
        tradeOrder.setNotifyInfo(result.getReturnInfo());
        tradeOrder.setStatus(result.getStatus().getStatus());
        tradeOrder.setUpdateTime(new Date());
        payTradeOrderMapper.updateById(tradeOrder);
        return new PayQueryResultDTO()
                .setTradeOrderNo(tradeOrder.getTradeOrderNo())
                .setPayAmount(tradeOrder.getPayAmount())
                .setStatus(TradeOrderStatusEnum.get(tradeOrder.getStatus()))
                .setPayTime(tradeOrder.getPayTime());
    }

    @Override
    public RefundResultDTO refund(RefundRequestDTO dto) {
        return null;
    }

    @Override
    public void notify(TradeNotifyDTO dto) {

    }
}
